/*
 * Albareth - an action roleplaying game.
 * Copyright (C) 2008 Jens Anuth
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 *   02110-1301, USA.
 * 
 * $Id: Brain.java 46 2008-05-15 18:09:39Z jens464 $
*/
package albareth.core.brain;


import java.util.Iterator;

import albareth.core.Game;
import albareth.core.action.Action;
import albareth.core.entity.Activatable;
import albareth.core.entity.Mobile;
import albareth.core.entity.MobileController;
import albareth.core.entity.PlayerCharacter;
import albareth.util.Location;
import albareth.util.Pathfinder;
import albareth.util.SimplePathfinder;
import frosch.util.Direction;

public abstract class Brain implements MobileController, Cloneable
{
    //the minimal distance to player when approach gets triggered.
    public static final int APPROACH_DISTANCE = 4; 

    public static final int MODE_SLEEPING   = 0;
    public static final int MODE_ATTACK     = 1;
    public static final int MODE_FLEE       = 2;
    public static final int MODE_WANDER     = 3;
    public static final int MODE_WALK_TO    = 4;
    
    private int _mode = MODE_WANDER;
    
    private boolean _playerInSight = false;
    private boolean _playerSpotted = false;
    private boolean _playerApproached = false;
    
    protected Mobile _owner;
    private Pathfinder _pathfinder = SimplePathfinder.getInstance();

    private Mobile _targetMobile = null;
    private Location _targetLocation = null;


    Brain()
    {
    }
    
    public void setControlledMobile(Mobile owner)
    {
        _owner = owner;
    }
    
    /* begin targeting stuff */
    
    public Mobile getTargetMobile()
    {
        return _targetMobile;
    }
    
    public void setTargetMobile(Mobile target)
    {
        _targetMobile = target;
    }
    
    public void clearTargetMobile()
    {
        _targetMobile = null;
    }
    
    public Location getTargetLocation()
    {
        return (_targetMobile != null) ? _targetMobile.getLocation() :
            _targetLocation;
    }

    public void setTargetLocation(Location target)
    {
        _targetLocation = new Location(target);
    }
    
    public void clearTargetLocation()
    {
        _targetLocation = null;
    }

    
    public int getMode()
    {
        return _mode;
    }

    public void setMode(int mode)
    {
        _mode = mode;
    }

    public void act()
    {
        lookForPlayer();

        switch(_mode)
        {
            case MODE_ATTACK:
                processAttack();
                break;
            case MODE_FLEE:
                processFlee();
                break;
            case MODE_WANDER:
                processWander();
                break;
            case MODE_WALK_TO:
                processWalkTo();
                break;
            default:
        }
    }

    /**
     * Walks towards the set target location. Does nothing, if arrived.
     */
    private void processWalkTo()
    {
        if (!_owner.canWalk()) {
            return;
        }
        
        Location targetLoc = getTargetLocation(); 
        
        //no target loc or already there?
        if (targetLoc == null || _owner.getLocation().equals(targetLoc)) {
            return;
        }

        int dir = _pathfinder.getNextStep(
                _owner.getLocation(), getTargetLocation()
        );
        _owner.tryWalk(dir);
    }

    /**
     * Wanders randomly, but never out of range of its home.
     */
    private void processWander()
    {
        //if wandered too far from home, return to it.
        if (_owner.getHome() != null)
        {
            if (_owner.getLocation().distance(_owner.getHome()) >=
                    _owner.getMaxHomeDist())
            {
                setTargetLocation(_owner.getHome());
                processWalkTo();
                return;
            }
        }

        _owner.tryWalk(Direction.getRandomDirection());
        
        lookForPlayer();
    }

    private void lookForPlayer()
    {
        PlayerCharacter player = Game.getContext().getPlayer(); 
        _playerInSight = player.hasLineOfSight(_owner.getLocation());

        if (_playerInSight)
        {
            notifySpot(Game.getContext().getPlayer());
            
            //deactivated this because approach shall only be triggered when
            //the player approaches the mob, not the other way.
            /*
            if (_playerApproached == false &&
                    _owner.getLocation().distance(player.getLocation()) <
                        Brain.APPROACH_DISTANCE) 
            {
                _playerApproached = true;
                notifyApproached(Game.getContext().getPlayer());
            }
            */

        
        }
        

        
    }

    private void processFlee()
    {
        // TODO Auto-generated method stub
        
    }

    private void processAttack()
    {
        //already casting sth?
        Action activeAction = _owner.getActiveAction(); 
        if (activeAction != null)
        {
            //if the enemy is out of sight and it is a harmful action against him,
            //abort it to approach its last known location.
            if (_playerInSight) {
                return; //continue to cast.
            } else if (activeAction.isFlagSet(Action.HARMFUL)) {
                _owner.getActiveAction().abort();
            }
        }

        if (_playerInSight) {
            //can i cast something? then do it.
            Activatable activatable = activateNextAvailable();
            if (activatable != null) {
                return;
            }
        }

        updateTargets();

        //nothing found to cast? then charge.
        
        if (getTargetMobile() != null &&
                _owner.getDistance(getTargetMobile()) < 2)         //try a melee hit?
        {
            _owner.tryMeleeAttack(getTargetMobile());
        
        } else if (getTargetLocation() != null) //or walk?
        {
            //target location reached? (but player not there)
            if (getTargetLocation().equals(_owner.getLocation()))
            {
                //...and no LoS, then abort attack.
                if (_playerInSight == false)
                {
                    setMode(MODE_WANDER);
                    clearTargetLocation();
                    clearTargetMobile();
                    _playerSpotted = false;
                    _playerApproached = false;
                }
                
            } else { //else walk there
                processWalkTo();
            }
        }
        
    }
    

    private void updateTargets()
    {
        PlayerCharacter player = Game.getContext().getPlayer();

        if (_playerInSight) {
            setTargetMobile(player);
            setTargetLocation(player.getLocation());
        } else {
            clearTargetMobile();
        }
    }

    public final void notifySpot(Mobile spottedMobile)
    {
        if (!_playerSpotted) {
            onSpot(spottedMobile);
            _playerSpotted = true;
        }
    }
    
    public final void notifyApproached(Mobile approachingMobile)
    {
        onGetApproached(approachingMobile);
    }

    public void notifyAttacked(Mobile attacker)
    {
        if (getTargetMobile() == attacker) {
            return; //we already know we are in combat with them.
        }
        setTargetMobile(attacker);
        onGetAttacked(attacker);
    }
    
    public void notifyNearDeath()
    {
        onNearDeath();
    }



    public abstract void onSpot(Mobile spottedMobile);
    
    public abstract void onGetApproached(Mobile approachingMobile);
    
    public abstract void onNearDeath();
    
    public abstract void onGetAttacked(Mobile attacker);
    
    public abstract void onHear(Location soundLocation);

    

    /*
    public Mobile getControlledMobile()
    {
        return _owner;
    }*/


    
    
    public boolean hasPanic()
    {
        return (_owner.getHitpoints() <  _owner.getMaxHitpoints()/8 ||
                _owner.isFlagSet(Mobile.FLAG_FEARED));
    }
    
    
    public Activatable activateNextAvailable()
    {
        //TODO: also decide about using useable items.
        //TODO: global cooldown needed for monster spells.monster-cooldown-group
        for (Iterator it = _owner.getSpells().iterator(); it.hasNext(); )
        {
            Activatable spell = (Activatable)it.next();
            
            if (spell.isReady() && spell.activate(_owner)) {
                return spell;
            }
        }
        return null;
    }


    
    
    
    public Object clone() throws CloneNotSupportedException
    {
        return super.clone();
    }






}
